const WebRtcStreamer = (function () {
  /**
   * Interface with WebRTC-streamer API
   * @constructor
   * @param {string} videoElement - id of the video element tag
   * @param {string} srvurl -  url of webrtc-streamer (default is current location)
   */
  class WebRtcStreamer {
    constructor(videoElement, srvurl) {
      if (typeof videoElement === "string") {
        this.videoElement = document.getElementById(videoElement);
      } else {
        this.videoElement = videoElement;
      }
      this.srvurl =
        srvurl ||
        location.protocol +
          "//" +
          window.location.hostname +
          ":" +
          window.location.port;
      this.pc = null;

      this.mediaConstraints = {
        offerToReceiveAudio: true,
        offerToReceiveVideo: true,
      };

      this.iceServers = null;
      this.earlyCandidates = [];
    }
    _handleHttpErrors(response) {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      return response;
    }
    /**
     * Connect a WebRTC Stream to videoElement
     * @param {string} videourl - id of WebRTC video stream
     * @param {string} audiourl - id of WebRTC audio stream
     * @param {string} options  -  options of WebRTC call
     * @param {string} stream   -  local stream to send
     * @param {string} prefmime -  prefered mime
     */
    connect(videourl, audiourl, options, localstream, prefmime) {
      this.disconnect();

      // getIceServers is not already received
      if (!this.iceServers) {
        // console.log('Get IceServers')
        fetch(this.srvurl + "/api/getIceServers")
          .then(this._handleHttpErrors)
          .then((response) => response.json())
          .then((response) =>
            this.onReceiveGetIceServers(
              response,
              videourl,
              audiourl,
              options,
              localstream,
              prefmime,
            ),
          )
          .catch((error) => this.onError("getIceServers " + error));
      } else {
        this.onReceiveGetIceServers(
          this.iceServers,
          videourl,
          audiourl,
          options,
          localstream,
          prefmime,
        );
      }
    }
    /**
     * Disconnect a WebRTC Stream and clear videoElement source
     */
    disconnect() {
      if (this.videoElement?.srcObject) {
        this.videoElement.srcObject.getTracks().forEach((track) => {
          track.stop();
          this.videoElement.srcObject.removeTrack(track);
        });
      }
      if (this.pc) {
        fetch(this.srvurl + "/api/hangup?peerid=" + this.pc.peerid)
          .then(this._handleHttpErrors)
          .catch((error) => this.onError("hangup " + error));

        try {
          this.pc.close();
        } catch (e) {
          console.log("Failure close peer connection:" + e);
        }
        this.pc = null;
      }
    }
    /*
     * GetIceServers callback
     */
    onReceiveGetIceServers(
      iceServers,
      videourl,
      audiourl,
      options,
      stream,
      prefmime,
    ) {
      this.iceServers = iceServers;
      this.pcConfig = iceServers || { iceServers: [] };
      try {
        this.createPeerConnection();

        var callurl =
          this.srvurl +
          "/api/call?peerid=" +
          this.pc.peerid +
          "&url=" +
          encodeURIComponent(videourl);
        if (audiourl) {
          callurl += "&audiourl=" + encodeURIComponent(audiourl);
        }
        if (options) {
          callurl += "&options=" + encodeURIComponent(options);
        }

        if (stream) {
          this.pc.addStream(stream);
        }

        // clear early candidates
        this.earlyCandidates.length = 0;

        // create Offer
        this.pc.createOffer(this.mediaConstraints).then(
          (sessionDescription) => {
            // console.log('Create offer:' + JSON.stringify(sessionDescription))
            // console.log(
            //   `video codecs:${Array.from(
            //     new Set(RTCRtpReceiver.getCapabilities('video')?.codecs?.map((codec) => codec.mimeType))
            //   )}`
            // )
            // console.log(
            //   `audio codecs:${Array.from(
            //     new Set(RTCRtpReceiver.getCapabilities('audio')?.codecs?.map((codec) => codec.mimeType))
            //   )}`
            // )
            if (prefmime != undefined) {
              //set prefered codec
              const [prefkind] = prefmime.split("/");
              const codecs = RTCRtpReceiver.getCapabilities(prefkind).codecs;
              const preferedCodecs = codecs.filter(
                (codec) => codec.mimeType === prefmime,
              );

              // console.log(`preferedCodecs:${JSON.stringify(preferedCodecs)}`)
              this.pc
                .getTransceivers()
                .filter(
                  (transceiver) => transceiver.receiver.track.kind === prefkind,
                )
                .forEach((tcvr) => {
                  if (tcvr.setCodecPreferences != undefined) {
                    tcvr.setCodecPreferences(preferedCodecs);
                  }
                });
            }

            this.pc.setLocalDescription(sessionDescription).then(
              () => {
                fetch(callurl, {
                  method: "POST",
                  body: JSON.stringify(sessionDescription),
                })
                  .then(this._handleHttpErrors)
                  .then((response) => response.json())
                  .catch((error) => this.onError("call " + error))
                  .then((response) => this.onReceiveCall(response))
                  .catch((error) => this.onError("call " + error));
              },
              (error) => {
                console.log(
                  "setLocalDescription error:" + JSON.stringify(error),
                );
              },
            );
          },
          (error) => {
            alert("Create offer error:" + JSON.stringify(error));
          },
        );
      } catch (e) {
        this.disconnect();
        alert("connect error: " + e);
      }
    }
    getIceCandidate() {
      fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid)
        .then(this._handleHttpErrors)
        .then((response) => response.json())
        .then((response) => this.onReceiveCandidate(response))
        .catch((error) => this.onError("getIceCandidate " + error));
    }
    /*
     * create RTCPeerConnection
     */
    createPeerConnection() {
      // console.log('createPeerConnection  config: ' + JSON.stringify(this.pcConfig))
      this.pc = new RTCPeerConnection(this.pcConfig);
      var pc = this.pc;
      pc.peerid = Math.random();

      pc.onicecandidate = (evt) => this.onIceCandidate(evt);
      pc.onaddstream = (evt) => this.onAddStream(evt);
      pc.oniceconnectionstatechange = (evt) => {
        console.log(
          "oniceconnectionstatechange  state: " + pc.iceConnectionState,
        );
        if (this.videoElement) {
          if (pc.iceConnectionState === "connected") {
            this.videoElement.style.opacity = "1.0";
            console.log(
              "\x1b[32m%s\x1b[0m",
              "oniceconnectionstatechange  state: " + pc.iceConnectionState,
            );
          } else if (pc.iceConnectionState === "disconnected") {
            this.videoElement.style.opacity = "0.25";
            console.log(
              "\x1b[31m%s\x1b[0m",
              "oniceconnectionstatechange  state: " + pc.iceConnectionState,
            );
          } else if (
            pc.iceConnectionState === "failed" ||
            pc.iceConnectionState === "closed"
          ) {
            this.videoElement.style.opacity = "0.5";
          } else if (pc.iceConnectionState === "new") {
            this.getIceCandidate();
          }
        }
      };
      pc.ondatachannel = function (evt) {
        // console.log('remote datachannel created:' + JSON.stringify(evt))
        evt.channel.onopen = function () {
          // console.log('remote datachannel open')
          this.send("remote channel openned");
        };
        evt.channel.onmessage = function (event) {
          // console.log('remote datachannel recv:' + JSON.stringify(event.data))
        };
      };

      try {
        var dataChannel = pc.createDataChannel("ClientDataChannel");
        dataChannel.onopen = function () {
          // console.log('local datachannel open')
          this.send("local channel openned");
        };
        dataChannel.onmessage = function (evt) {
          // console.log('local datachannel recv:' + JSON.stringify(evt.data))
        };
      } catch (e) {
        console.log("Cannor create datachannel error: " + e);
      }

      // console.log('Created RTCPeerConnnection with config: ' + JSON.stringify(this.pcConfig))
      return pc;
    }
    /*
     * RTCPeerConnection IceCandidate callback
     */
    onIceCandidate(event) {
      if (event.candidate) {
        if (this.pc.currentRemoteDescription) {
          this.addIceCandidate(this.pc.peerid, event.candidate);
        } else {
          this.earlyCandidates.push(event.candidate);
        }
      } else {
        // console.log('End of candidates.')
      }
    }
    addIceCandidate(peerid, candidate) {
      fetch(this.srvurl + "/api/addIceCandidate?peerid=" + peerid, {
        method: "POST",
        body: JSON.stringify(candidate),
      })
        .then(this._handleHttpErrors)
        .then((response) => response.json())
        .then((response) => {
          // console.log('addIceCandidate ok:' + response)
        })
        .catch((error) => this.onError("addIceCandidate " + error));
    }
    /*
     * RTCPeerConnection AddTrack callback
     */
    onAddStream(event) {
      // console.log('Remote track added:' + JSON.stringify(event))
      this.videoElement.srcObject = event.stream;
      var promise = this.videoElement.play();
      if (promise !== undefined) {
        promise.catch((error) => {
          console.warn("error:" + error);
          this.videoElement.setAttribute("controls", true);
        });
      }
    }
    /**
     * AJAX /call callback
     */
    onReceiveCall(dataJson) {
      // console.log('offer: ' + JSON.stringify(dataJson))
      var descr = new RTCSessionDescription(dataJson);
      this.pc.setRemoteDescription(descr).then(
        () => {
          // console.log('setRemoteDescription ok')
          while (this.earlyCandidates.length) {
            var candidate = this.earlyCandidates.shift();
            this.addIceCandidate(this.pc.peerid, candidate);
          }

          this.getIceCandidate();
        },
        (error) => {
          console.log("setRemoteDescription error:" + JSON.stringify(error));
        },
      );
    }
    /*
     * AJAX /getIceCandidate callback
     */
    onReceiveCandidate(dataJson) {
      // console.log('candidate: ' + JSON.stringify(dataJson))
      if (dataJson) {
        for (var i = 0; i < dataJson.length; i++) {
          var candidate = new RTCIceCandidate(dataJson[i]);

          // console.log("Adding ICE candidate :" + JSON.stringify(candidate) );
          this.pc.addIceCandidate(candidate).then(
            () => {
              // console.log('addIceCandidate OK')
            },
            (error) => {
              console.log("addIceCandidate error:" + JSON.stringify(error));
            },
          );
        }
        this.pc.addIceCandidate();
      }
    }
    /*
     * AJAX callback for Error
     */
    onError(status) {
      console.log("onError:" + status);
    }
  }

  return WebRtcStreamer;
})();

if (typeof window !== "undefined" && typeof window.document !== "undefined") {
  window.WebRtcStreamer = WebRtcStreamer;
}
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
  module.exports = WebRtcStreamer;
}
